package b3d.parsers
{
	import b3d.core.Object3D;
	import b3d.geometry.Mesh3D;
	import b3d.geometry.MeshUtils;
	import b3d.materials.AmbientLightMaterial;
	import b3d.materials.DiffuseMaterial;
	import b3d.materials.SpecularLightMaterial;
	import b3d.materials.lighting.Ambient;
	
	import flash.events.Event;
	import flash.geom.Matrix3D;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.Endian;
	import flash.utils.getTimer;
	
	import mx.core.mx_internal;

	public class Parser_3DS extends BaseParser
	{
		private var data			:ByteArray;
		private var currentMeshData	:MeshData;
		
		public var geometry			:Vector.<Mesh3D>;
		public var geometryDic		:Dictionary;
		
		public const EDIT3DS		:int=0x3D3D;
		public const MESH			:int=0x4000;
		public const MESH_OBJECT	:int=0x4100;
		public const MESH_VERTICES	:int=0x4110;
		public const MESH_FACES		:int=0x4120;
		public const MESH_TEX_VERT	:int=0x4140;
		public const MESH_MATRIX	:int=0x4160;		
		
		private var vertsCache:Dictionary;
		private var idsCache:Dictionary;
		private var uvsCache:Dictionary;
		
		public function Parser_3DS(){}		
		
		
		public override function parseFromUrl(url:String, onParseComplete:Function):void
		{
			this.url 				= url;
			this.onParseComplete 	= onParseComplete;
			var loader:URLLoader 	= new URLLoader(new URLRequest(url));
			loader.dataFormat 		= URLLoaderDataFormat.BINARY;
			
			loader.addEventListener(Event.COMPLETE, onLoadComplete);	
		}
		private function onLoadComplete(e:Event):void
		{
			var data:ByteArray = (e.target as URLLoader).data;
			parseFromData(data);
		}
		public function parseFromData(data:ByteArray):void
		{
			this.data = data;
			data.endian = Endian.LITTLE_ENDIAN;
			data.position = 0;
			
			objects = new Vector.<Object3D>();
			geometry = new Vector.<Mesh3D>();
			geometryDic = new Dictionary();
			
			var time:int = getTimer();
			
			var chunk:Chunk3ds=new Chunk3ds();
			readChunk(chunk);
			parse3DS(chunk);
			
			trace("parse time", getTimer()-time);		
			if(onParseComplete != null) onParseComplete();
		}
		
		/**PARSING**/		
		private function parse3DS(chunk:Chunk3ds):void
		{
			while(chunk.bytesRead < chunk.length)
			{
				var subChunk:Chunk3ds = new Chunk3ds();
				readChunk(subChunk);
				switch(subChunk.id)
				{
					case EDIT3DS:
						parseEdit3DS(subChunk);
						break;
					default:
						skipChunk(subChunk);
				}
				chunk.bytesRead+=subChunk.length;
			}
		}
		private function parseEdit3DS(chunk:Chunk3ds):void
		{
			while(chunk.bytesRead < chunk.length)
			{
				var subChunk:Chunk3ds = new Chunk3ds();
				readChunk(subChunk);
				switch(subChunk.id)
				{
					case MESH:
						
						currentMeshData = new MeshData();
						currentMeshData.name=readASCIIZString(data);
						subChunk.bytesRead+=currentMeshData.name.length + 1;
						
						
						vertsCache = new Dictionary();
						idsCache = new Dictionary();
						uvsCache = new Dictionary();
						
						currentMeshData.vertices = new Vector.<Number>();
						currentMeshData.ids = new Vector.<uint>();
						currentMeshData.uvs = new Vector.<Number>();
						// meshData.materials = new Array(); 
						
						parseMesh(subChunk, currentMeshData);
						buildMesh(currentMeshData);
//						var object:Object3D = new Object3D().build(mesh, new AmbientLightMaterial(Math.random()*0xFFFFFF));
						var object:Object3D = new Object3D().build(mesh, new DiffuseMaterial());
						object.transform = currentMeshData.matrix;
						objects.push(object);
						break;
					default:
						skipChunk(subChunk);
				}
				chunk.bytesRead+=subChunk.length;
			}
		}
		private function buildMesh(currentMeshData:MeshData):void
		{
			if(currentMeshData.ids.length == 0) return;
			mesh = new Mesh3D().build(	currentMeshData.ids,
				currentMeshData.vertices,
				null,
				currentMeshData.uvs);
			
			MeshUtils.generateVertexNormals(mesh);
			geometryDic[currentMeshData.name] = mesh;
			geometry.push(mesh);
		}
		private function parseMesh(chunk:Chunk3ds, meshData:MeshData):void
		{
			while (chunk.bytesRead < chunk.length)
			{
				var subChunk:Chunk3ds=new Chunk3ds();
				readChunk(subChunk);
				switch (subChunk.id)
				{
					case MESH_OBJECT:
						parseMesh(subChunk, meshData);
						break;
					case MESH_VERTICES:
						readMeshVertices(subChunk, meshData);
						break;
					case MESH_FACES:
						readMeshFaces(subChunk, meshData);
						parseMesh(subChunk, meshData);
						break;
					case MESH_TEX_VERT:
						readMeshTexVert(subChunk, meshData); 
						break;
					case MESH_MATRIX:
						parseMatrix(subChunk, meshData);
						//skipChunk(subChunk);
						break;
					default:
						skipChunk(subChunk);
				}
				chunk.bytesRead+=subChunk.length;
			}
		}
		private function readMeshVertices(chunk:Chunk3ds, meshData:MeshData):void
		{
			var numVerts:int = data.readUnsignedShort();
			chunk.bytesRead+=2;
			
			for (var i:int=0; i < numVerts; i++)
			{
				var x:Number = data.readFloat();
				var z:Number = data.readFloat();
				var y:Number = data.readFloat();
				
				meshData.vertices.push(x,y,z);
				var hash:String = x+","+y+","+z;
				if(vertsCache[hash] == undefined)
				{
					vertsCache[hash] = i;
				}else{
					idsCache[i] = vertsCache[hash];
				}				
				chunk.bytesRead+=12;
			}
		}
		private function readMeshTexVert(chunk:Chunk3ds, meshData:MeshData):void
		{
			var numUVs:int=data.readUnsignedShort();
			chunk.bytesRead+=2;
//			var uvsDic:Dictionary = new Dictionary();
			for (var i:int=0; i < numUVs; i++)
			{
				var u:Number = data.readFloat();
				var v:Number = 1-data.readFloat();
				var hash:String = u.toString()+","+v.toString();
				/*
				hash = u+","+v;
				uvsCache[i/2] = hash;
				if(uvsDic[hash] == undefined)
				{
					uvsDic[hash] = i;
				}else{
//					trace("NOT UNIQUE");
//					trace(meshData.vertices[uvsDic[hash]*3], meshData.vertices[(uvsDic[hash]*3)+1], meshData.vertices[(uvsDic[hash]*3)+2]);
				}
				*/
				meshData.uvs.push(u,v);
				chunk.bytesRead+=8;
			}
		}
		private function readMeshFaces(chunk:Chunk3ds, meshData:MeshData):void
		{
			var numFaces:int=data.readUnsignedShort();
			chunk.bytesRead+=2;
			var numpolys:int;
			for (var i:int=0; i < numFaces; i++)
			{
				var v0:uint=data.readUnsignedShort();
				var v1:uint=data.readUnsignedShort();
				var v2:uint=data.readUnsignedShort();
//				
//				trace(v0, v1, v2);
//				trace(meshData.vertices[v0*3], meshData.vertices[(v0*3)+1], meshData.vertices[(v0*3)+2]);
//				trace(meshData.vertices[v1*3], meshData.vertices[(v1*3)+1], meshData.vertices[(v1*3)+2]);
//				trace(meshData.vertices[v2*3], meshData.vertices[(v2*3)+1], meshData.vertices[(v2*3)+2]);
//				
//				trace("__");
				
				if(idsCache[v0] != undefined)v0 = idsCache[v0]; 
				if(idsCache[v1] != undefined)v1 = idsCache[v1]; 
				if(idsCache[v2] != undefined)v2 = idsCache[v2]; 
				
				var visible:Boolean=(data.readUnsignedShort() as Boolean);
				chunk.bytesRead+=8;
				
				
				
				
				meshData.ids.push(v0, v1, v2);
			}
		}
		private function parseMatrix(chunk:Chunk3ds, meshData:MeshData):void
		{			
			//3ds matrix is 3x4 with and assumed 0,0,0,1 4th column
			var values:Vector.<Number> = new Vector.<Number>(16, true);			
			values[0] = data.readFloat();
			values[1] = data.readFloat();
			values[2] = data.readFloat();
			values[3] = 0;
			
			values[4] = data.readFloat();
			values[5] = data.readFloat();
			values[6] = data.readFloat();
			values[7] = 0;
			
			values[8] = data.readFloat();
			values[9] = data.readFloat();
			values[10] = data.readFloat();
			values[11] = 0;
			
			values[12] = data.readFloat();
			values[14] = data.readFloat();		//swapped \/
			values[13] = data.readFloat();		//swapped /\
			values[15] = 1;
			
			var matrix:Matrix3D = new Matrix3D(values);
			var invese:Matrix3D = matrix.clone();
			invese.invert();
			
			invese.transformVectors(meshData.vertices, meshData.vertices);
			
			chunk.bytesRead+=52;			
			meshData.matrix = matrix;
		}
		private function readChunk(chunk:Chunk3ds):void
		{
			chunk.id = data.readUnsignedShort();
			chunk.length = data.readUnsignedInt();
			chunk.bytesRead = 6;
		}
		private function skipChunk(chunk:Chunk3ds):void
		{
			data.position += chunk.length - chunk.bytesRead;
			chunk.bytesRead = chunk.length;
		}
		private function readASCIIZString(data:ByteArray):String
		{
			var readLength:int=0; // length of string to read 
			var l:int=data.length - data.position;
			var tempByteArray:ByteArray = new ByteArray();
			
			for (var i:int=0; i < l; i++)
			{
				var c:int=data.readByte();
				
				if (c == 0)
				{
					break;
				}
				tempByteArray.writeByte(c);
			}			
			var asciiz:String="";
			tempByteArray.position=0;
			for (i=0; i < tempByteArray.length; i++)
			{
				asciiz+=String.fromCharCode(tempByteArray.readByte());
			}
			return asciiz;
		}
	}
}
import flash.geom.Matrix3D;

//helper classes
class Chunk3ds
{
	public var id			:int;
	public var length		:int;
	public var bytesRead	:int;
}
class MeshData
{
	public var name:String;
	public var ids			:Vector.<uint>;
	public var vertices		:Vector.<Number>;
	public var normals		:Vector.<Number>;
	public var uvs			:Vector.<Number>;
	public var matrix		:Matrix3D;
}